AgentDeck · Design Proposal · v1
Surface — Android E-ink (B&W + Color Kaleido 3)
유지보수 가능한 e-ink 레이아웃 제안

한 모델,
한 화면,
두 종이.

iOS/Android와의 외형 동기화는 e-ink에서 실패한다.
공통화 대상은 의미 모델이지 픽셀 위치가 아니다.
Targets
Crema S · Onyx · Kobo · Pantone 6 (Kaleido 3)
Scope
EinkMonitorScreen · projection · refresh policy
Related
docs/android-ui.md · MonitorScreen (tablet)
진단

왜 LCD/LED 레이아웃을 e-ink에 그대로 얹으면 깨지는가

iOS/Android tablet의 MonitorScreen오버레이색상으로 정보를 압축한다. 두 가지 모두 e-ink에서 비싸다. 화면을 통일하려는 시도는 결국 같은 4가지 충돌을 만든다.

01
PHYSICS

오버레이는 고스팅을 만든다

반투명 HUD 패널은 LCD에서 자연스럽지만, EPD에서는 alpha 합성이 부분 갱신과 결합되며 잔상을 누적시킨다. GC16 full refresh로 지우는 순간 사용자는 200–500ms의 검은 깜박임을 본다.

02
PALETTE

색상은 의미를 전달하지 못한다

B&W 16-grey, Kaleido 3 4096-color는 LCD 채도와 다르다. 빨강=거부, 초록=승인 같은 순수 색 의존 코드는 색약 사용자뿐 아니라 e-ink에서도 무너진다. 글리프와 텍스트가 권위를 가져야 한다.

03
DENSITY

14sp 미만의 글자는 읽히지 않는다

tablet HUD는 12sp 라벨이 흔하다. e-ink는 dither 한계 + 저대비로 작은 글자가 무너진다. “같은 픽셀 위치”를 강제하면 정보가 가독성에 양보된다.

04
REFRESH

실시간 영역과 정적 영역이 섞인다

크리처 애니메이션은 A2(10fps), 게이지는 DU(2s), 세션 패널은 거의 정적이다. 이를 한 레이어에서 그리면 가장 빠른 zone의 잔상이 가장 느린 zone으로 번진다.

05
MAINTENANCE

두 트리가 하나처럼 보이려고 한다

MonitorScreenEinkMonitorScreen이 같은 외형을 흉내내려 할수록 공유 코드가 줄어든다. e-ink는 자기 형태를 가진 viewer일 때 가장 적은 코드로 정확하다.

06
DEVICE FAN-OUT

벤더 EPD API가 다르다

Rockchip EinkManager, Onyx BaseDevice, KOReader RK35xxEPDController — refresh mode 호출 표면이 셋이다. UI가 직접 호출하면 디바이스 분기가 UI 코드로 새어 나온다.

원칙

E-ink는 모방하지 않는다.
읽힌다.

네 가지 원칙을 코드 경계로 강제한다. 이것이 깨지는 코드 변경은 review에서 reject한다.

01

Shared semantics, separate pixels

DashboardState · FocusAction · AttentionRequest는 공통. 레이아웃은 surface마다 자유.

02

Slots over overlays

반투명 오버레이 금지. 화면은 명시적 zone으로만 분할. 각 zone은 자기 refresh policy를 갖는다.

03

Glyph + text first, color last

승인/거부/대기/연결 모두 글리프와 라벨로 1차 표현. 색은 color e-ink에서 추가 강조용일 뿐.

04

Vendor EPD via one port

UI는 EpdRefreshController만 호출. Rockchip/Onyx/Kobo/Color 분기는 driver 모듈 내부에 갇혀 있다.

아키텍처

Refresh-Zone Layered Projection

레이어 구조

┌────────────────────────────────────────────────────┐ │ shared / state-model (공통) │ │ DashboardState · FocusAction · AttentionRequest │ │ Timeline · UsageGauge · AgentRow │ └────────────────────────────────────────────────────┘ ▲ ▲ │ │ ┌─────────────┴──────────┐ ┌──────────┴──────────────┐ │ MonitorScreen (LCD) │ │ EinkMonitorScreen │ │ - free overlays │ │ - fixed slot grid │ │ - 60fps motion │ │ - per-zone refresh │ │ - color = primary │ │ - glyph = primary │ └────────────────────────┘ └────────┬────────────────┘ │ uses ┌────────▼────────────┐ │ EpdRefreshController│ port ├─────────────────────┤ │ RockchipEpdDriver │ │ OnyxEpdDriver │ │ KoreaderEpdDriver │ │ ColorKaleidoDriver │ └─────────────────────┘

Zone 종류

ZoneRefresh역할
CHROME DU · 2 s 로고, 세션 목록, settings 진입. 거의 안 변함.
FOCUS A2 · 200 ms 현재 선택한 세션의 model · state · project.
TERRARIUM A2/anim · 10 fps 크리처 애니메이션. 상태 전환에서만 GC16 cleanup.
GAUGES DU · 2 s 5h/7d 게이지, OAuth/Ollama 모델 카탈로그.
TIMELINE A2 · 300 ms 이벤트 로그. append-only이므로 부분 갱신 친화적.
ATTENTION GC16 once awaiting_* 진입 시 한 번 full refresh, 답변 옵션 명료화.

각 zone은 RefreshHint를 emit하고 EpdRefreshController가 디바이스별로 번역한다. UI 코드는 벤더 분기 0줄.

레이아웃

화면은 슬롯이다.
오버레이는 없다.

Chrome — Title bar
AgentDeck · timestamp · battery · Settings · rotate
DU · static
Chrome — Session List (28%)
DU · static
agent rows (primary + siblings)
tap → focus_session
attention pip (◆) when sibling awaits
Focus + Terrarium (Body)
A2 · live
• Focus card (top, pinned) — model · project · state · attention
• Terrarium frame (bottom) — 크리처는 여기서만 움직인다
ATTENTION 발생 시 focus card가 이 영역을 22→34%로 확장
Gauges + Models
DU · 2s
5h · 7d 블록 게이지 · OAuth catalog · Ollama running
Timeline
A2 · append-only
이벤트 한 줄씩 push. 8줄 도달 시 위에서 한 줄 drop.
목업 — B&W

Crema S · Onyx · Kobo

16-grey · landscape 1404×1872 dp 등가 · GC16 + DU + A2 혼합

14:32
● 84%
Sessions · 4
claude · web PROC
claude · cli IDLE
codex IDLE
openclaw ROUTE
Topology
↑ hooks · gateway
↓ SD+ · D200H · TC001
PROJECT · agentdeck
claude-code · web
opus-4.1 ● PROCESSING session 14:18 · 14m
claude·web
Limits · 5 h
48%
↻ 1h 02m
Limits · 7 d
23%
↻ 4d 10h
Models
OAuth: opus-4.1 · sonnet-4 · haiku-4.5
Ollama: qwen3:30b · bge-m3
14:31Read apme/runner.ts
14:31·model call opus-4.1
14:32·IDLE → PROCESSING
14:32eval result · coding · 0.78
State · IDLE/PROCESSINGRefresh budget · ~1.4 fps avg
14:33
◆ ATTENTION ● 84%
Sessions · 4
claude · web IDLE
claude · cli AWAIT
codex PROC
openclaw SIT
PROJECT · scratch-render
claude-code · cli
sonnet-4 ◆ AWAITING permission prompt
PERMISSION REQUEST · 3 OPTIONS
Allow Bash(rm -rf .zig-cache) in this project?
[Y] Approve once [A] Always for this project [N] Deny
claude·cli ◆
5 h
48%
7 d
23%
Models · active 3
Claude · OpenClaw Gateway · qwen3:30b
14:32·PROCESSING → AWAITING
14:33permission · Bash(rm -rf …)
14:33·focus → claude·cli
State · AWAITING (single GC16 on entry)Glyph + label · 색 의존 0%
목업 — Color e-ink

Pantone 6 · Kaleido 3

4096 색 · A2 animation mode · 같은 레이아웃, 의미 매핑은 색을 강조로만 사용. B&W 화면을 그대로 켰을 때 정보가 손실되지 않는 것이 ground truth.

14:32
● 84%
Sessions · 4
claude · web PROC
claude · cli IDLE
codex IDLE
openclaw ROUTE
PROJECT · agentdeck
claude-code · web
opus-4.1 ● PROCESSING 14m
claude·web
Limits · 5 h
48%
↻ 1h 02m
Limits · 7 d
23%
↻ 4d 10h
Models
OAuth: opus-4.1 · sonnet-4 · haiku-4.5
Ollama: qwen3:30b · bge-m3
14:31Read apme/runner.ts
14:32·IDLE → PROCESSING
14:32eval · coding · 0.78
Color = enhancement onlySame layout · same glyphs
14:33
◆ ATTENTION ● 84%
Sessions · 4
claude · web IDLE
claude · cli AWAIT
codex PROC
openclaw SIT
PROJECT · scratch-render
claude-code · cli
sonnet-4 ◆ AWAITING permission
PERMISSION REQUEST · 3 OPTIONS
Allow Bash(rm -rf .zig-cache) in this project?
[Y] Approve once [A] Always [N] Deny
claude·cli ◆
5 h
48%
7 d
23%
Active
Claude · OpenClaw · qwen3:30b
14:33permission · Bash(rm -rf …)
Color = enhancement onlyGlyph still works in B&W
유지보수성

UI는 hint를 쏘고,
드라이버가 번역한다.

Before — UI에 벤더 분기가 새어 나옴

// EinkAquariumFrame.kt — 현재 형태
if (manufacturer == "rockchip") {
  einkManager.setMode("12") // A2
  einkManager.sendOneFullFrame()
} else if (manufacturer == "onyx") {
  device.setViewDefaultUpdateMode(A2_MODE)
} else if (isColorEink) {
  // skip dither, use animation mode
  ...
}

UI 컴포넌트가 디바이스 SPI를 직접 부른다. 새 e-ink 기기 추가 = UI 코드 수정.

After — Zone이 hint를 emit, controller가 routing

// EinkMonitorScreen.kt
EinkZone(id = "terrarium", hint = RefreshHint.Anim(fps = 10)) {
  TerrariumProjection(state)
}
EinkZone(id = "attention", hint = RefreshHint.FullOnce) {
  AttentionProjection(state.attention)
}

// EpdRefreshController.kt — driver port
interface EpdRefreshController {
  fun apply(zone: ZoneId, hint: RefreshHint)
}
// RockchipEpdDriver · OnyxEpdDriver · ColorKaleidoDriver
// 새 기기 추가 = driver 한 파일 추가

공유되는 것 / 분리되는 것

레이어LCD/LED와 공유?위치설명
DashboardState · FocusAction · AttentionRequest SHARED shared/state-model WS 프로토콜에서 직접 derived. 모든 surface가 같은 store를 읽는다.
Creature sprite paths · Timeline parser SHARED shared/render SVG path → Compose path / Canvas drawRect fallback. 한 source.
Layout · zones · refresh hints EINK ONLY ui/eink/projection tablet/iOS와 모양이 달라야 한다. 흉내내지 않는다.
EpdRefreshController · vendor drivers EINK ONLY ui/eink/epd Rockchip · Onyx · Kaleido 분기는 여기서 끝난다. UI는 모른다.
Color palette · glyph table SHARED w/ fallback shared/tokens 같은 의미 키, B&W 매핑 + color e-ink 매핑이 한 테이블에 있음.
실행 계획

점진적으로 옮긴다.
한 번에 갈아엎지 않는다.

01

Extract EpdRefreshController

현재 EinkRenderer·EinkAnimatedRefreshZone 안에 흩어진 벤더 호출을 driver port로 모은다. UI 변경 없음. 위험 낮음.

~1 sprint
02

Introduce EinkZone primitive

공통 wrapper로 zone을 감싼다. 기존 EinkMonitorScreen의 5 영역을 순차적으로 zone으로 마이그레이션. 화면은 그대로.

~1 sprint
03

Reflow → fixed slot grid

오버레이를 모두 slot으로 변환. Attention zone을 GC16 once 정책으로 도입. Crema/Pantone QA를 같이 돌려 잔상 회귀 확인.

~1.5 sprint
04

Glyph-first state coding

색에만 의존하는 state 표현 제거. Settings에서 “색 강조 끄기” 토글로 회귀 테스트.

~0.5 sprint
05

Color enhancement layer

Kaleido 3에서만 활성화되는 token override layer. B&W 화면을 켜면 자동으로 빠진다.

~0.5 sprint
06

Driver fan-out

Onyx · Kobo 드라이버 추가. 각 1 PR. UI/projection 변경 없이 새 디바이스 지원.

ongoing
결과적으로
UI 한 트리, EPD 분기 0줄. e-ink는 자기 형태로, 의미는 한 모델로.
Proposal · ready for review